<script setup>
import { onMounted, ref, watch } from "vue";
import { Graph, Path } from "@antv/x6";
import { register } from '@antv/x6-vue-shape'
import { Selection } from '@antv/x6-plugin-selection'
import { History } from '@antv/x6-plugin-history'
import { Dnd } from '@antv/x6-plugin-dnd'
import nodeModel from "./NodeModel.vue";
const graph = ref(null);
const timer = ref(null)
const infoCtx = ref()
const openDialog = ref(false)
const props = defineProps({
  newNode: {
    type: Object,
  },
})
watch(() => props.newNode,
  (newNode, o) => {
    newNode && addNode(newNode)
  },
  { immediate: true }
)
register({
  shape: 'dag-node',
  width: 180,
  height: 36,
  component: nodeModel,
  ports: {
    groups: {
      left: {
        position: 'left',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#C2C8D5',
            strokeWidth: 1,
            fill: '#fff',
          },
        },
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#C2C8D5',
            strokeWidth: 1,
            fill: '#fff',
          },
        },
      },
    },
  }
}, {
  shape: 'dag-in',
  width: 180,
  height: 36,
  component: nodeModel,
  ports: {
    groups: {
      left: {
        position: 'left',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#C2C8D5',
            strokeWidth: 1,
            fill: '#fff',
          },
        },
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#C2C8D5',
            strokeWidth: 1,
            fill: '#fff',
          },
        },
      },
    },
  }
},);
Graph.registerEdge(
  "dag-edge",
  {
    inherit: "edge",
    attrs: {
      line: {
        stroke: "#C2C8D5",
        strokeWidth: 1,
        targetMarker: null,
      },
    },
  },
  true
);
Graph.registerConnector(
  'algo-connector',
  (sourcePoint, targetPoint) => {
    const hgap = Math.abs(targetPoint.x - sourcePoint.x)
    const path = new Path()
    path.appendSegment(
      Path.createSegment('M', sourcePoint.x - 4, sourcePoint.y),
    )
    path.appendSegment(
      Path.createSegment('L', sourcePoint.x + 12, sourcePoint.y),
    )
    path.appendSegment(
      Path.createSegment(
        'C',
        sourcePoint.x < targetPoint.x
          ? sourcePoint.x + hgap / 2
          : sourcePoint.x - hgap / 2,
        sourcePoint.y,
        sourcePoint.x < targetPoint.x
          ? targetPoint.x - hgap / 2
          : targetPoint.x + hgap / 2,
        targetPoint.y,
        targetPoint.x - 6,
        targetPoint.y,
      ),
    )
    path.appendSegment(
      Path.createSegment('L', targetPoint.x + 2, targetPoint.y),
    )
    return path.serialize()
  },
  true,
)

const initCanvas = () => {
  const container = document.getElementById("container");
  if (!container) {
    return;
  }
  graph.value = new Graph({
    container,
    width: container.offsetWidth,
    height: 900,
    background: {
      color: "#EFF4FF",
    },
    panning: {
      enabled: true,
      eventTypes: ["leftMouseDown", "mouseWheel"],
    },
    mousewheel: true,
    grid: {
      visible: true,
    },
    connecting: {
      connector: 'algo-connector',
      snap: { radius: 50 },
      allowBlank: false,
      allowLoop: false,
      allowNode: false,
      allowEdge: false,
      allowMulti: true,
      highlight: true,
      createEdge() {
        return graph.value.createEdge({
          shape: "dag-edge",
          attrs: {
            line: {
              strokeDasharray: "5 5",
            },
          },
          zIndex: -1,
        });
      },
    },
  });
  graph.value.use(
    new Selection({
      multiple: true,
      rubberEdge: true,
      rubberNode: true,
      modifiers: 'shift',
      rubberband: true,
    })
  ).use(
    new History({
      enabled: true,
    }),
  ).use(new Dnd({}))
  graph.value.on('edge:connected', ({ edge }) => {
    console.log("边--连线")
    edge.attr({
      line: {
        strokeDasharray: '',
      },
    })
  })
  graph.value.on('blank:click', ({ e }) => {
    console.log("单击画布空白区域")
    openDialog.value = false
  })
  graph.value.on('edge:contextmenu', ({ e, x, y, edge, view }) => {
    console.log("边--右键")
    infoCtx.value = edge
    openDialog.value = true
  })
  graph.value.on("node:contextmenu", ({ e, x, y, node, view }) => {
    console.log("节点--右键")
    infoCtx.value = node
    openDialog.value = true
  });
  graph.value.on("node:click", ({ e, x, y, node, view }) => {
    console.log("节点--单击")
  });
  graph.value.on("node:port:click", ({ e, x, y, node, view }) => {
    console.log("连接桩--单击")
  });
  graph.value.on("edge:dblclick", ({ e, x, y, node, view }) => {
    console.log("边--双击")
  });
  graph.value.on('node:change:data', ({ node }) => {
    const edges = graph.value.getIncomingEdges(node)
    const { status } = node.getData()
    edges?.forEach((edge) => {
      if (status === 'running') {
        edge.attr('line/strokeDasharray', 5)
        edge.attr('line/style/animation', 'running-line 30s infinite linear')
      } else {
        edge.attr('line/strokeDasharray', '')
        edge.attr('line/style/animation', '')
      }
    })
  })

};
const initData = [
  {
    id: "1",
    shape: "dag-node",
    x: 200,
    y: 300,
    data: { label: "输入节点", status: "success" },
    ports: [{ id: "1-1", group: "right" }],

  },
  {
    id: "2",
    shape: "dag-node",
    x: 500,
    y: 300,
    data: { label: "算法节点", status: "success" },
    ports: [
      { id: "2-1", group: "left" },
      { id: "2-2", group: "right" },
    ],
  },
  {
    id: "3",
    shape: "dag-node",
    x: 800,
    y: 300,
    data: { label: "输出节点", status: "success" },
    ports: [{ id: "3-1", group: "left" }],
  },
  {
    id: "4",
    shape: "dag-edge",
    source: { cell: "1", port: "1-1" },
    target: { cell: "2", port: "2-1" },
    zIndex: 0,
  },
  {
    id: "5",
    shape: "dag-edge",
    source: { cell: "2", port: "2-2" },
    target: { cell: "3", port: "3-1" },
    zIndex: 0,
  },
];
const init = (data) => {
  const cells = data.map((item) => {
    if (item.shape === "dag-node") {
      return graph.value.createNode(item);
    }
    if (item.shape === "dag-in") {
      return graph.value.createNode(item);
    }
    return graph.value.createEdge(item);
  });
  graph.value.resetCells(cells);
};

const nodeStatusList = [
  [
    { id: "1", status: "running" },
    { id: "2", status: "default" },
    { id: "3", status: "default" },
  ],
  [
    { id: "1", status: "success" },
    { id: "2", status: "running" },
    { id: "3", status: "default" },
  ],
  [
    { id: "1", status: "success" },
    { id: "2", status: "success" },
    { id: "3", status: "running" },
  ],
  [
    { id: "1", status: "success" },
    { id: "2", status: "success" },
    { id: "3", status: "failed" },
  ],
];
const showNodeStatus = async (statusList) => {
  const status = statusList.shift();
  if (status) {
    status.forEach(({ id, status }) => {
      const node = graph.value.getCellById(id);
      const data = node && node.getData();
      node && node.setData({
        ...data,
        status,
      });
    });
    timer.value = setTimeout(() => {
      showNodeStatus(statusList);
    }, 3000);
  }
};

function addNode(option) {
  graph.value.addNode(option);
}
function fit() {
  const num = 1 - graph.value.zoom();
  num > 1 ? graph.value.zoom(num * -1) : graph.value.zoom(num);
  // graph.value.centerContent();
}
function beforeStep() {
  graph.value.undo();
}
function nextStep() {
  graph.value.redo();
}

function changeZoom(num) {
  graph.value.zoom(num);
}
function handleStart(item) {
  timer.value && clearTimeout(timer.value);
  init(item || initData);
  showNodeStatus(Object.assign([], nodeStatusList));
  graph.value.centerContent();
}
function saveDag() {
  localStorage.setItem(
    "x6DAG",
    JSON.stringify(graph.value.toJSON({ diff: true }))
  );
}
function handleLoad() {
  timer.value && clearTimeout(timer.value);
  const x6Dag = JSON.parse(localStorage.getItem("x6DAG"));
  if (!x6Dag) {
    return;
  }
  handleStart(x6Dag.cells);
}
function exJSON() {
  console.log(graph.value.toJSON(), "exJSON")
}
function imJSON() {
  console.log(graph.value, "imJSON")
}
function clearSvg() {
  graph.value.clearCells();
}
function del(e) {
  if (e.shape == "dag-edge") {
    graph.value.removeEdge(e.id);
    openDialog.value = false
  } else {
    graph.value.removeNode(e.id);
    openDialog.value = false
  }
}
onMounted(() => {
  initCanvas();
  init(initData);
  showNodeStatus(Object.assign([], nodeStatusList))
});
</script>

<template>
  <div class="canvas">
    <div class="btn">
      <button @click="beforeStep()">撤销</button>
      <button @click="nextStep()">回退</button>
      <button @click="changeZoom(0.2)">放大</button>
      <button @click="changeZoom(-0.2)">缩小</button>
      <button @click="fit()">适应屏幕</button>
      <button @click="handleStart()">执行</button>
      <button @click="saveDag()">保存</button>
      <button @click="handleLoad()">加载保存页面</button>
      <button @click="exJSON()">导出JSON</button>
      <button @click="imJSON()">导入JSON</button>
      <button @click="clearSvg()">清空画布</button>
    </div>
    <div id="container"></div>
    <dialog :open="openDialog" class="dialog">
      <button @click="del(infoCtx)">
        删除
      </button>
    </dialog>
  </div>
</template>

<style scoped> 
.canvas {
   width: 85vw;
 }

 .dialog {
   top: 520px;
   width: 90px;
   height: 100px;
   border-radius: 5px;
 }

 .btn button {
   margin: 10px;
   display: inline-block;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    background: #fff;
    border: 1px solid #dcdfe6;
    color: #606266;
    text-align: center;
    padding: 10px;
    font-size: 14px;
    border-radius: 4px;
 }
 .btn button:hover {
  background: #66b1ff;
    border-color: #66b1ff;
    color: #fff;
 }
</style>
